﻿//#define VERBOSE
using System;
using System.Diagnostics;
using RayDen.Library.Components.Color.Types;
using RayDen.Library.Components.Surface;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Corrector.Managers;
using RayDen.RayEngine.Engines.PathTracer;
using RayEngine.Spectral.Entities;

namespace RayEngine.Spectral.Engine.Integrators.PathTracer
{
    public class PathTracerSampler : BasePathSampler
    {
        public PathTracerIntegrator Integrator;
        public PurePathTracerPathState PathState;
        private bool specularBounce;
        private int tracedShadowRayCount, maxDepth;

        private ShadePointInfo hitInfo;
        private float RussianRuletteImportanceCap;

        public override void InitPath(IPathProcessor buffer)
        {
            base.InitPath(buffer);
            maxDepth = Buffer.Scene.MaxPathDepth;
            RussianRuletteImportanceCap = Buffer.Scene.RussianRuletteImportanceCap;
            this.Radiance = ColorManager.Instance.Zero();
            this.Throughput = ColorManager.Instance.Unit();
            this.PathState = PurePathTracerPathState.EyeVertex;
            //if (this.secRays == null) this.secRays = new PurePathTracerPath.ShadowRayInfo[scene.ShadowRayCount];
            this.Sample = Buffer.Sampler.GetSample(this.Sample);
            IRay ray;
            Buffer.Scene.Camera.GetRay(Sample.imageX, Sample.imageY, out ray);
            this.PathRay = (RayData)ray;
            this.RayIndex = -1;
            //this.pathWeight = 1.0f;
            this.tracedShadowRayCount = 0;
            this.Depth = 0;
            this.specularBounce = true;
        }

        public override bool FillRayBuffer(RayBuffer rayBuffer)
        {
            var leftSpace = rayBuffer.LeftSpace();
            if (((PathState == PurePathTracerPathState.EyeVertex) && (1 > leftSpace)) ||
            ((PathState == PurePathTracerPathState.ShadowRaysOnly) && (tracedShadowRayCount > leftSpace)) ||
            ((PathState == PurePathTracerPathState.NextVertex) && (tracedShadowRayCount + 1 > leftSpace)))
                return false;
            if (PathState != PurePathTracerPathState.ShadowRaysOnly)
                RayIndex = rayBuffer.AddRay(ref PathRay);
            /*
            if (PathState == PurePathTracerPathState.NextVertex || PathState == PurePathTracerPathState.ShadowRaysOnly)
            {
                for (int i = 0; i < tracedShadowRayCount; ++i)
                    secRays[i].currentShadowRayIndex = rayBuffer.AddRay(ref secRays[i].shadowRay);
            }*/
            return true;
        }
        public override void Advance(RayBuffer rayBuffer, SampleBuffer consumer)
        {
            int currentTriangleIndex = 0;
#if VERBOSE

            try {
#endif

            base.Advance(rayBuffer, consumer);

            RayHit rayHit = rayBuffer.rayHits[RayIndex];

            Depth++;
            bool missed = rayHit.Index == 0xffffffffu;
            var wo = -PathRay.Dir;
            if (missed || Depth > maxDepth)
            {
                if (specularBounce && missed)
                {
                    Radiance.Add(Throughput.Mul(ColorManager.Instance.Convert(this.SampleEnvironment(ref wo))));
                }
                Splat(consumer);

                return;
            }

            HitManager.Instance.EvalHit(ref PathRay, ref rayHit, ref hitInfo);

            currentTriangleIndex = (int)rayHit.Index;

            //If Hit light)
            if (hitInfo.PointType == ShadePointType.Light)
            {
                float lpdf;
                var lt = this.Buffer.SceneManager.GetLightById(hitInfo.ObjectId);
                var le = lt.Light.Emit(ref wo, out lpdf);

                Radiance.Add(Throughput.Mul(le));
                Splat(consumer);

                return;
            }

            var hitPoint = PathRay.Point(rayHit.Distance);

            tracedShadowRayCount = 0;

            float fPdf;
            Vector wi;

            float f;
            BsdfEvent @event;
            hitInfo.Brdf.Sample_f(hitInfo,
                                    Sample.GetLazyValue(), Sample.GetLazyValue(), Sample.GetLazyValue(),
                                    out wi, out f, out fPdf, out @event);

            if ((fPdf <= 0.0f) || f<=0f)
            {
                if (tracedShadowRayCount > 0)
                    PathState = PurePathTracerPathState.ShadowRaysOnly;
                else
                {
                    Splat(consumer);

                }
                return;
            }

            Throughput.Mul(hitInfo.GetColorInstance()).Mul(f / fPdf);

            if (Depth > maxDepth)
            {
                float prob = Math.Max(Throughput.Filter(), RussianRuletteImportanceCap);
                if (prob >= Sample.GetLazyValue())
                {
                    Throughput.Mul(1f / prob);
                }
                else
                {
                    if (tracedShadowRayCount > 0)
                        PathState = PurePathTracerPathState.ShadowRaysOnly;
                    else
                    {
                        Splat(consumer);

                    }

                    return;
                }
            }

            PathRay.Org = hitPoint;
            PathRay.Dir = wi.Normalize();
            PathState = PurePathTracerPathState.NextVertex;

#if VERBOSE
            }
            catch (Exception ex) {
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Advance path exception");
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Error triangle {0}", currentTriangleIndex);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.Message);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.StackTrace);

            }
#endif
        }

        private RgbSpectrumInfo SampleEnvironment(ref Vector vector)
        {
            return (RgbSpectrumInfo)this.Buffer.Scene.SampleEnvironment(ref vector);
        }

        

        public override void Splat(SampleBuffer sampleBuffer)
        {
            if (!this.Radiance.IsBlack())
            {
                var rgb = new RgbSpectrum();//= this.Radiance.ToXyz();
                this.Radiance.TransformToRgb(out rgb);

                sampleBuffer.SplatSample((float)this.Sample.imageX, (float)this.Sample.imageY, ref rgb);

            }
            InitPath(this.Buffer);
        }
    }
}